Chapter 15 - Input Variables
Parameterizing your code to make it more repeatable
Types
Primitive:
- string
- number
- bool
Complex:
- list
- map
Structural:
- object
- tuple
Basics
Variables typically go into a variables.tf file.
variable "variable_name" {
description = "Description of the Variable"
default = "defaultvalue"
}
In your variables.tf:
variable "resource_group_name" {
description = "Resource Group Name"
type = string
default = "myrg"
}
variable "resource_group_location" {
description = "Resource Group Location"
type = string
default = "East US"
}
In your RG.tf:
resource "azurerm_resource_group" "myrg" {
name = "${var.resource_group_name}_RG" # to append
location = var.resource_group_location # to call the variable
}
The theory here is that you can programmatically assign entirely different names of resources just based on editing a variables.tf file. You can take this one step further in the pipelines by sending the same code through the pipelines with different variables.
Assign when prompted
This will prompt you after you run the terraform plan/apply. You have a good chance of making a mistake if you run a tf apply and type in something like "sunbet" :) In your variables.tf:
variable "resource_group_name" {
description = "Resource Group Name"
type = string
# remove the default
}
variable "resource_group_location" {
description = "Resource Group Location"
type = string
# remove the default
}
Override with default variables via the CLI
terraform plan -var="resource_group_name=mysubbedname_rg"
Terraform.tfplan
Intro to the terraform plan -out terraform.tfplan
and the terrform apply terraform.tfplan
You ideally want to add this to the .gitignore so you don't commit that file.
Environment variables
You can set environment variables - subscription ID, etc and then refer to them via the terraform. You would need to unset the environment variables if running your terraform on the same machine
export TF_VAR_resoure_group_name=rgenv
export TF_VAR_resoure_group_location=westus2
export TF_VAR_virtual_network_name=vnetenv
export TF_VAR_subnet_name=subnetenv
echo $variablename
unset TF_VAR_resoure_group_name
unset TF_VAR_resoure_group_location
unset TF_VAR_virtual_network_name
unset TF_VAR_subnet_name
Tfvars file
You can include the variables in a terraform tfvars file inside of your code.
business_unit = "tax"
environment = "dev"
resoure_group_name = "rg"
resoure_group_location = "centralus"
virtual_network_name = "vnet"
subnet_name = "subnet"
Running a terraform plan here will flatten out all of the files and use the tfvars.
-var-file="file.tfvars"
You can separate out:
- dev.tfvars
- qa.tfvars
- prod.tfvars
and then run `terraform plan -var-file="qa.tfvars"
You can then run this with the terraform.tfvars file together to delineate between dev and qa.
There is an issue with this that we will cover later, using workspaces. If you build the dev resources, then run the qa resources, the dev resources will be destroyed in favor of the qa resources. You can create a dev, qa workspace to run each of these pieces of code in.
auto.tfvars
Adding auto to the tfvars makes them load by default alongside the terraform.tfvars.
Constructors like List and Map
Types and type constraints. List - [] Map -
You can use the list [] like this:
variable "virtual_network_address_space" {
description = "Virtual Network Address Space"
type = list(string)
default = ["10.0.0.0/16", "10.1.0.0/16", "10.2.0.0/16"]
}
resource "azurerm_virtual_network" "myvnet" {
name = "${var.business_unit}-${var.environment}-${var.virtual_network_name}"
#address_space = ["10.0.0.0/16"]
address_space = var.virtual_network_address_space # for all 3
# or
#address_space = [var.virtual_network_address_space[0]] #for one
location = azurerm_resource_group.myrg.location
resource_group_name = azurerm_resource_group.myrg.name
}
Maps
Maps are a group of key value pairs (objects). Default is a map below.
variable "public_ip_sku" {
description = "Azure Public IP Address SKU"
type = map(string)
default = {
"eastus" = "Basic",
"eastus2" = "Standard"
}
}
# Tags
variable "common_tags" {
description = "Common Tags for Azure Resources"
type = map(string)
default = {
"Tool" = "Terraform",
"Cloud" = "Azure"
}
}
# Resource
resource "azurerm_public_ip" "mypublicip" {
name = "mypublicip-1"
resource_group_name = azurerm_resource_group.myrg.name
location = azurerm_resource_group.myrg.location
allocation_method = "Static"
domain_name_label = "app1-vm-${random_string.myrandom.id}"
#sku = var.public_ip_sku["eastus"]
tags = var.common_tags
}
lookup
https://developer.hashicorp.com/terraform/language/functions/lookup
Element = List lookup = map :)
lookup(map, key, default)
= you use the key to look up something in the map, if it's not found, return the default.
If you are using keys that start with numbers, you need to use : instead of = From the previous example, sku can use lookup like so.
resource "azurerm_public_ip" "mypublicip" {
name = "mypublicip-1"
resource_group_name = azurerm_resource_group.myrg.name
location = azurerm_resource_group.myrg.location
allocation_method = "Static"
domain_name_label = "app1-vm-${random_string.myrandom.id}"
sku = lookup(var.public_ip_sku, var.resoure_group_location, "Basic")
tags = var.common_tags
}
# Functions
---
- length() - length of the string, number of items in a map or array
- substr() - can select a portion of the string (string, offset, number of characters)
- contains() - check to see if the input contains a value
- lower() - to lower
- upper() - TO UPPER
- regex()
- can()
# Custom Validation
---
```hcl
variable "resource_group_location" {
description = "Resource Group Location"
type = string
default = "eastus"
validation {
condition = var.resource_group_location == "eastus" || var.resource_group_location =="eastus2"
error_message = "We only allow Resources to be created in eastus or eastus2 locations."
}
}
You can also use contains() here as well :)
Regex and Can functions
condition = can(regex("india$", var.resoure_group_location))
Sensitive Variables
Exposed in plain text in your state file.
- Create a secrets.tfvars
- Put your secret variables in here
- Don't commit this file. run `terraform plan -var-file="secrets.tfvars" in our environment this might be our beconf.tfvars files.
Boolean
type = bool
default = true
Number
type = number
default = 18
Structural Types
https://developer.hashicorp.com/terraform/language/expressions/type-constraints
Objects
This is like defining a map of strings, you can create an object with different data types.
key = type
variable "os_configs" {
type = object({
location = string
size = string
instance_count = number
})
}
Tuples
A tuple is similar to an object but not in a key = value sense
Instead of key = type
it is simply type
.
["a", 15, true]
In the Terraform Code:
# DB Variables
db_name = "mydb12424"
db_storage_mb = 5120
db_auto_grow_enabled = true
# This is an example of an object
tdpolicy = {
enabled = true
retention_days = 10
email_account_admins = true
email_addresses = [ "solo@gmail.com", "liz@gmail.com" ]
}
# redefined as a tuple:
tdpolicy = [true, 10, true, [ "solo@gmail.com", "liz@gmail.com" ]]
Defined as a variable:
# 12. Azure MySQL DB Threat Detection Policy (Variable Type: tuple)
variable "tdpolicy" {
description = "Azure MySQL DB Threat Detection Policy"
type = tuple([bool, number, bool, list(string) ])
}
and in the resource:
resource "azurerm_mysql_server" "mysqlserver" {
name = "${var.business_unit}-${var.environment}-${var.db_name}"
...
...
threat_detection_policy {
enabled = var.tdpolicy[0]
retention_days = var.tdpolicy[1]
email_account_admins = var.tdpolicy[2]
email_addresses = var.tdpolicy[3]
}
Set
A group of unique values.
variable "environment" {
description = "Environment Name"
type = set(string)
default = ["dev1", "qa1", "staging1", "prod1"]
}
Then, you can create resources for each of those in the set:
# Resource-1: Azure Resource Group
resource "azurerm_resource_group" "myrg" {
for_each = var.environment
name = "${var.business_unit}-${each.key}-${var.resoure_group_name}"
location = var.resoure_group_location
}
# Resource 2: VNet
resource "azurerm_virtual_network" "myvnet" {
for_each = var.environment
name = "${var.business_unit}-${each.key}-${var.virtual_network_name}"
address_space = ["10.0.0.0/16"]
location = azurerm_resource_group.myrg[each.key].location
resource_group_name = azurerm_resource_group.myrg[each.key].name
}
# Resource 3: Subnet
resource "azurerm_subnet" "mysubnet" {
for_each = var.environment
#name = "mysubnet-1"
name = "${var.business_unit}-${each.key}-${var.virtual_network_name}-mysubnet"
resource_group_name = azurerm_resource_group.myrg[each.key].name
virtual_network_name = azurerm_virtual_network.myvnet[each.key].name
address_prefixes = ["10.0.2.0/24"]
}